
class MatrixError(Exception):
    """
    Exception class for Matrix
    """
    pass

class Matrix(object):
    """
    A simple Python matrix class with basic operation
    """

    def __init__(self, m, n, value=0):
        self.__mat = [[value] * n for i in xrange(m)]
        self.__m = m
        self.__n = n

    def __getitem__(self, idx):
        """ allow self[x][y] reference """
        return self.__mat[idx]

    def __setitem__(self, idx, value):
        """ allow self[x][y] setting """
        self.__mat[idx] = value

    def __str__(self):
        s = '\n'.join([' '.join([str(item) for item in row]) for row in self.__mat])
        return s + '\n'

    def __repr__(self):
        mat = str(self.__mat)
        size = str(self.size())
        return 'Matrix: "%s", Size: "%s"' % (mat, size)

    def __getattr__(self, name):
        """ Handle self.T as self.transpose() """
        if name == 'T':
            return self.transpose()
        raise MatrixError, 'Unkown attribute!'

    def size(self):
        """ Get matrix size """
        return (self.__m, self.__n)

    def transpose(self):
        """ Get Transposed matrix """
        res = Matrix(self.__n, self.__m)
        res.__mat = [list(item) for item in zip(*self.__mat)]
        return res

    def __eq__(self, mat):
        """ Test matrix equality """
        return self.__mat == mat.__mat

    def __add__(self, mat):
        """ Add two matrix """
        if self.size() != mat.size():
            raise MatrixError, 'Cant add on matrixes of different size!'

        res = Matrix(self.__m, self.__n)
        for row in xrange(self.__m):
            res[row] = [item[0] + item[1] for item in zip(self.__mat[row], mat.__mat[row])]

        return res

    def __sub__(self, mat):
        """ Sub two matrix """
        if self.size() != mat.size():
            raise MatrixError, 'Cant substract on matrixes of different size!'

        res = Matrix(self.__m, self.__n)
        for row in xrange(self.__m):
            res[row] = [item[0] - item[1] for item in zip(self.__mat[row], mat.__mat[row])]

        return res

    def __mul__(self, mat):
        """ dot product two matrix """
        if self.__n != mat.__m:
            raise MatrixError, 'Cant multiply on matrixes of different size!'

        mat_t = mat.transpose()
        res = Matrix(self.__m, mat.__n)
        for x in xrange(self.__m):
            for y in xrange(mat_t.__m):
                res[x][y] = sum([item[0] * item[1] for item in zip(self.__mat[x], mat_t.__mat[y])])
        return res

    def __iadd__(self, mat):
        """ Add matrix to this one """
        res = self + mat
        self.__mat = res.__mat
        return self

    def __isub__(self, mat):
        """ Sub matrix to this one """
        res = self - mat
        self.__mat = res.__mat
        return self

    def __imul__(self, mat):
        """ Sub matrix to this one """
        res = self - mat
        self.__mat = res.__mat
        return self


if __name__ == "__main__":
    M = Matrix(4, 3, value=3)
    M[1][2] = 6

    P = Matrix(4, 3, value=2)
    P[3][2] = 3

    print M
    print P
    print M + P
    M += P
    print M
    M -= P
    print M
    print M.T
    print P
    print M.T * P




